#include "nosal/core/optional.h"
using namespace netaos;
using nosal::core::Optional;
using nosal::core::nullopt;

#include "gtest/gtest.h"

#include <string>


TEST(OptionalTest, TypeIsAvailableAndCompilable)
{
    Optional<int> v1;
    Optional<std::string> v2;

    SUCCEED();
}

/*
TEST(OptionalTest, DoesNotCompile)
{
    // The code below should not compile
    nosal::core::nullopt_t nopt;
}
*/

TEST(OptionalTest, DisengagedCtor)
{
    Optional<int> v1;
    Optional<std::string> v2;

    ASSERT_TRUE(!v1);
    ASSERT_TRUE(!v2);

    Optional<int> v3{nullopt};
    Optional<std::string> v4{nullopt};

    ASSERT_TRUE(!v3);
    ASSERT_TRUE(!v4);

    Optional<int> v5 = nullopt;
    Optional<std::string> v6 = nullopt;

    ASSERT_TRUE(!v5);
    ASSERT_TRUE(!v6);

    Optional<int> v7 = v5;
    Optional<std::string> v8 = v6;
    ASSERT_TRUE(!v7);
    ASSERT_TRUE(!v8);

    Optional<int> v9 = std::move(v5);
    Optional<std::string> v10 = std::move(v6);
    ASSERT_TRUE(!v9);
    ASSERT_TRUE(!v10);
}

struct Stateful
{
    enum class State
    {
        DefaultConstructed,
        CopyConstructed,
        MoveConstructed
    };
    State m_state;
    int m_value;

    explicit Stateful(int value = 0)
        : m_state(State::DefaultConstructed)
        , m_value(value)
    { }
    Stateful(const Stateful& other)
        : m_state(State::CopyConstructed)
        , m_value(other.m_value)
    { }
    Stateful(Stateful&& other)
        : m_state(State::MoveConstructed)
        , m_value(std::move(other.m_value))
    { }
    Stateful& operator=(const Stateful&) = delete;
    Stateful& operator=(Stateful&&) = delete;
};

struct DefaultConstructible : Stateful
{
    explicit DefaultConstructible(int val = 0)
        : Stateful(val)
    { }
    DefaultConstructible(const DefaultConstructible&) = delete;
    DefaultConstructible(DefaultConstructible&&) = delete;
    DefaultConstructible& operator=(const DefaultConstructible&) = delete;
    DefaultConstructible& operator=(DefaultConstructible&&) = delete;
};

struct CopyConstructible : Stateful
{
    explicit CopyConstructible(int val = 0)
        : Stateful(val)
    { }
    CopyConstructible(const CopyConstructible&) = default;
    CopyConstructible(CopyConstructible&&) = default;
    CopyConstructible& operator=(const CopyConstructible&) = default;
    CopyConstructible& operator=(CopyConstructible&&) = default;
};

struct MoveConstructible : Stateful
{
    explicit MoveConstructible(int val = 0)
        : Stateful(val)
    { }
    MoveConstructible(const MoveConstructible&) = delete;
    MoveConstructible(MoveConstructible&&) = default;
    MoveConstructible& operator=(const MoveConstructible&) = delete;
    MoveConstructible& operator=(MoveConstructible&&) = default;
};

static const int g_intTest{111};
static const std::string g_strTest{"test"};
static const Stateful g_valTest{g_intTest};

TEST(OptionalTest, ValueCtor)
{
    Optional<int> v1{g_intTest};
    Optional<std::string> v2{g_strTest};
    Optional<Stateful> v3{g_valTest};

    ASSERT_FALSE(!v1);
    ASSERT_FALSE(!v2);
    ASSERT_FALSE(!v3);
    ASSERT_EQ(*v1, g_intTest);
    ASSERT_EQ(*v2, g_strTest);
    ASSERT_EQ((*v3).m_state, Stateful::State::CopyConstructed);
    ASSERT_EQ(v3->m_state, Stateful::State::CopyConstructed);

    auto tint{g_intTest};
    auto tstr{g_strTest};
    auto tval{g_valTest};
    Optional<int> v4{std::move(tint)};
    Optional<std::string> v5{std::move(tstr)};
    Optional<Stateful> v6{std::move(tval)};

    ASSERT_FALSE(!v4);
    ASSERT_FALSE(!v5);
    ASSERT_FALSE(!v6);
    ASSERT_EQ(*v4, g_intTest);
    ASSERT_EQ(*v5, *v2);
    ASSERT_EQ((*v6).m_state, Stateful::State::MoveConstructed);
    ASSERT_EQ(v6->m_state, Stateful::State::MoveConstructed);
}

TEST(OptionalTest, CopyCtor)
{
    Optional<int> v1{g_intTest};
    Optional<std::string> v2{g_strTest};
    Optional<Stateful> v3{g_valTest};

    Optional<int> c1{v1};
    Optional<std::string> c2{v2};
    Optional<Stateful> c3{v3};

    ASSERT_FALSE(!c1);
    ASSERT_FALSE(!c2);
    ASSERT_FALSE(!c3);
    ASSERT_EQ(*c1, g_intTest);
    ASSERT_EQ(*c2, g_strTest);
    ASSERT_EQ(c3->m_value, g_intTest);

    Optional<CopyConstructible> v4{CopyConstructible{g_intTest}};
    Optional<CopyConstructible> c4{v4};
    ASSERT_EQ(c4->m_value, g_intTest);

    // non-copy-constructible T should NOT have Optional<T> copy-constructor
    // Uncomment to test that the compilation fails
    /*
    Optional<MoveConstructible> v5{g_intTest};
    Optional<MoveConstructible> c5{v5};
    Optional<DefaultConstructible> v6{g_intTest};
    Optional<DefaultConstructible> c6{v6};
    */
}

TEST(OptionalTest, MoveCtor)
{
    Optional<int> c1{Optional<int>{g_intTest}};
    Optional<std::string> c2{Optional<std::string>{g_strTest}};
    Optional<Stateful> c3{Optional<Stateful>{Stateful{g_intTest}}};

    ASSERT_FALSE(!c1);
    ASSERT_FALSE(!c2);
    ASSERT_FALSE(!c3);
    ASSERT_EQ(*c1, g_intTest);
    ASSERT_EQ(*c2, g_strTest);
    ASSERT_EQ(c3->m_value, g_intTest);

    Optional<CopyConstructible> c4{Optional<CopyConstructible>{CopyConstructible{g_intTest}}};
    ASSERT_EQ(c4->m_value, g_intTest);

    Optional<MoveConstructible> c5{Optional<MoveConstructible>{MoveConstructible{g_intTest}}};
    ASSERT_EQ(c5->m_value, g_intTest);

    // non-move-constructible T should NOT have Optional<T> copy-constructor
    // Uncomment to test that the compilation fails
    /*
    Optional<DefaultConstructible> c6{Optional<DefaultConstructible>{g_intTest}};
    */
}

TEST(OptionalTest, InplaceCtor)
{
    Optional<std::string> c1{nosal::core::in_place, "C-style string", 7ul};
    Optional<std::string> c2{nosal::core::in_place, {'C', '-', 's', 't', 'y', 'l', 'e'}};

    ASSERT_FALSE(!c1);
    ASSERT_FALSE(!c2);
    ASSERT_EQ(*c1, "C-style");
    ASSERT_EQ(*c2, "C-style");
}

TEST(OptionalTest, emplace)
{
    Optional<std::string> c1;
    Optional<std::string> c2;

    ASSERT_TRUE(!c1);
    ASSERT_TRUE(!c2);

    c1.emplace("C-style string", 7ul);
    c2.emplace({'C', '-', 's', 't', 'y', 'l', 'e'});

    ASSERT_FALSE(!c1);
    ASSERT_FALSE(!c2);
    ASSERT_EQ(*c1, "C-style");
    ASSERT_EQ(*c2, "C-style");
}

TEST(OptionalTest, is_optional)
{
    Optional<int> v1;
    Optional<std::string> v2;
    Optional<Stateful> v3;
    ASSERT_TRUE(nosal::core::is_optional<decltype(v1)>::value);
    ASSERT_TRUE(nosal::core::is_optional<decltype(v2)>::value);
    ASSERT_TRUE(nosal::core::is_optional<decltype(v3)>::value);

    ASSERT_FALSE(nosal::core::is_optional<decltype(g_intTest)>::value);
    ASSERT_FALSE(nosal::core::is_optional<decltype(g_strTest)>::value);
    ASSERT_FALSE(nosal::core::is_optional<decltype(g_valTest)>::value);

    Optional<int> tv1{g_intTest};
    Optional<std::string> tv2{g_strTest};
    Optional<Stateful> tv3{g_valTest};
    ASSERT_TRUE(nosal::core::is_optional<decltype(tv1)>::value);
    ASSERT_TRUE(nosal::core::is_optional<decltype(tv2)>::value);
    ASSERT_TRUE(nosal::core::is_optional<decltype(tv3)>::value);
}

TEST(OptionalTest, Assignment)
{
    Optional<int> v1;
    Optional<std::string> v2;
    Optional<Stateful> v3;

    ASSERT_TRUE(!v1);
    ASSERT_TRUE(!v2);
    ASSERT_TRUE(!v3);

    Optional<int> tv1{g_intTest};
    Optional<std::string> tv2{g_strTest};
    Optional<Stateful> tv3{g_valTest};
    v1 = tv1;
    v2 = tv2;
    v3 = tv3;
    ASSERT_FALSE(!v1);
    ASSERT_FALSE(!v2);
    ASSERT_FALSE(!v3);
    ASSERT_EQ(*v1, g_intTest);
    ASSERT_EQ(*v2, g_strTest);
    ASSERT_EQ((*v3).m_state, Stateful::State::CopyConstructed);

    v1 = nullopt;
    v2 = nullopt;
    v3 = nullopt;
    ASSERT_TRUE(!v1);
    ASSERT_TRUE(!v2);
    ASSERT_TRUE(!v3);

    // from const T&
    v1 = g_intTest;
    v2 = g_strTest;
    v3 = g_valTest;
    ASSERT_FALSE(!v1);
    ASSERT_FALSE(!v2);
    ASSERT_FALSE(!v3);
    ASSERT_EQ(*v1, g_intTest);
    ASSERT_EQ(*v2, g_strTest);

    // from T&
    v1 = const_cast<int&>(g_intTest);
    v2 = const_cast<std::string&>(g_strTest);
    v3 = const_cast<Stateful&>(g_valTest);
    ASSERT_FALSE(!v1);
    ASSERT_FALSE(!v2);
    ASSERT_FALSE(!v3);
    ASSERT_EQ(*v1, g_intTest);
    ASSERT_EQ(*v2, g_strTest);

    v1 = std::move(tv1);
    v2 = std::move(tv2);
    v3 = std::move(tv3);
    ASSERT_FALSE(!v1);
    ASSERT_FALSE(!v2);
    ASSERT_FALSE(!v3);
    ASSERT_FALSE(!tv1);
    ASSERT_FALSE(!tv2);
    ASSERT_FALSE(!tv3);
    ASSERT_EQ(*v1, g_intTest);
    ASSERT_EQ(*tv2, std::string());
    ASSERT_EQ(*v2, g_strTest);
    ASSERT_EQ((*v3).m_state, Stateful::State::MoveConstructed);

    v1 = {};
    v2 = {};
    v3 = {};
    ASSERT_TRUE(!v1);
    ASSERT_TRUE(!v2);
    ASSERT_TRUE(!v3);

    v1 = Optional<int>{g_intTest};
    v2 = Optional<std::string>{g_strTest};
    v3 = Optional<Stateful>{g_valTest};
    ASSERT_FALSE(!v1);
    ASSERT_FALSE(!v2);
    ASSERT_FALSE(!v3);
    ASSERT_EQ(*v1, g_intTest);
    ASSERT_EQ(*v2, g_strTest);
    ASSERT_EQ((*v3).m_state, Stateful::State::MoveConstructed);

    v1.reset();
    v2.reset();
    v3.reset();
    ASSERT_TRUE(!v1);
    ASSERT_TRUE(!v2);
    ASSERT_TRUE(!v3);

    auto tint{g_intTest};
    auto tstr{g_strTest};
    auto tval{g_valTest};
    v1 = std::move(tint);
    v2 = std::move(tstr);
    v3 = std::move(tval);
    ASSERT_FALSE(!v1);
    ASSERT_FALSE(!v2);
    ASSERT_FALSE(!v3);
    ASSERT_EQ(*v1, g_intTest);
    ASSERT_EQ(*v2, g_strTest);
    ASSERT_EQ((*v3).m_state, Stateful::State::MoveConstructed);
}

TEST(OptionalTest, value_or)
{
    Optional<int> v1;
    Optional<std::string> v2;
    Optional<Stateful> v3;
    const int tint{999};
    const std::string tstr{"or_test"};

    EXPECT_NO_THROW(v1.value_or(int{tint}));
    EXPECT_NO_THROW(v2.value_or(std::string{tstr}));
    EXPECT_NO_THROW(v3.value_or(Stateful{tint}));
    ASSERT_EQ(v1.value_or(int{tint}), tint);
    ASSERT_EQ(v2.value_or(std::string{tstr}), tstr);
    ASSERT_EQ(v3.value_or(Stateful{tint}).m_value, tint);

    v1 = g_intTest;
    v2 = g_strTest;
    v3 = g_valTest;

    EXPECT_NO_THROW(v1.value_or(int{tint}));
    EXPECT_NO_THROW(v2.value_or(std::string{tstr}));
    EXPECT_NO_THROW(v3.value_or(Stateful{tint}));
    ASSERT_EQ(v1.value_or(int{tint}), g_intTest);
    ASSERT_EQ(v2.value_or(std::string{tstr}), g_strTest);
    ASSERT_EQ(v3.value_or(Stateful{tint}).m_value, g_valTest.m_value);

    // for copy constructible both forms of value_or should be available
    Optional<CopyConstructible> v5;
    EXPECT_NO_THROW(v5.value_or(CopyConstructible{tint}));
    ASSERT_EQ(v5.value_or(CopyConstructible{tint}).m_value, tint);
    EXPECT_NO_THROW(Optional<CopyConstructible>{}.value_or(CopyConstructible{tint}));
    ASSERT_EQ(Optional<CopyConstructible>{}.value_or(CopyConstructible{tint}).m_value, tint);

    // for move-constructible at least one form should be available
    EXPECT_NO_THROW(Optional<MoveConstructible>{}.value_or(MoveConstructible{tint}));
    ASSERT_EQ(Optional<MoveConstructible>{}.value_or(MoveConstructible{tint}).m_value, tint);

    // non-copy- and non-move- constructible shouldn't have value_or()
    // Uncomment to test that the compilation fails
    /*
    EXPECT_NO_THROW(Optional<DefaultConstructible>{}.value_or(0));
    Optional<DefaultConstructible> v4;
    EXPECT_NO_THROW(v4.value_or(0));
    */
}

TEST(OptionalTest, Comparison)
{
    Optional<int> i0;
    Optional<int> i1{1};
    Optional<int> i2{2};

    // ==
    ASSERT_EQ(i0, i0);
    ASSERT_EQ(i1, i1);
    ASSERT_EQ(i2, i2);
    ASSERT_FALSE(i0 == i1);
    ASSERT_FALSE(i1 == i0);
    ASSERT_FALSE(i0 == i2);
    ASSERT_FALSE(i2 == i0);
    ASSERT_FALSE(i1 == i2);
    ASSERT_FALSE(i2 == i1);
    // !=
    ASSERT_NE(i0, i1);
    ASSERT_NE(i1, i0);
    ASSERT_NE(i0, i2);
    ASSERT_NE(i2, i0);
    ASSERT_NE(i1, i2);
    ASSERT_NE(i2, i1);
    ASSERT_FALSE(i0 != i0);
    ASSERT_FALSE(i1 != i1);
    ASSERT_FALSE(i2 != i2);
    // <
    ASSERT_LT(i0, i1);
    ASSERT_LT(i0, i2);
    ASSERT_LT(i1, i2);
    ASSERT_FALSE(i0 < i0);
    ASSERT_FALSE(i1 < i0);
    ASSERT_FALSE(i2 < i0);
    ASSERT_FALSE(i2 < i1);
    ASSERT_FALSE(i1 < i1);
    ASSERT_FALSE(i2 < i2);
    // <=
    ASSERT_LE(i0, i0);
    ASSERT_LE(i0, i1);
    ASSERT_LE(i0, i2);
    ASSERT_LE(i1, i1);
    ASSERT_LE(i1, i2);
    ASSERT_LE(i2, i2);
    ASSERT_FALSE(i1 <= i0);
    ASSERT_FALSE(i2 <= i0);
    ASSERT_FALSE(i2 <= i1);
    // >
    ASSERT_GT(i1, i0);
    ASSERT_GT(i2, i0);
    ASSERT_GT(i2, i1);
    ASSERT_FALSE(i0 > i0);
    ASSERT_FALSE(i0 > i1);
    ASSERT_FALSE(i0 > i2);
    ASSERT_FALSE(i1 > i1);
    ASSERT_FALSE(i1 > i2);
    ASSERT_FALSE(i2 > i2);
    // >=
    ASSERT_GE(i0, i0);
    ASSERT_GE(i1, i0);
    ASSERT_GE(i1, i1);
    ASSERT_GE(i2, i0);
    ASSERT_GE(i2, i1);
    ASSERT_GE(i2, i2);
    ASSERT_FALSE(i0 >= i1);
    ASSERT_FALSE(i0 >= i2);
    ASSERT_FALSE(i1 >= i2);
}

TEST(OptionalTest, ComparisonNull)
{
    Optional<int> i0;
    Optional<int> i1{1};

    // ==
    ASSERT_EQ(i0, nullopt);
    ASSERT_EQ(nullopt, i0);
    ASSERT_FALSE(i1 == nullopt);
    ASSERT_FALSE(nullopt == i1);
    // !=
    ASSERT_NE(i1, nullopt);
    ASSERT_NE(nullopt, i1);
    ASSERT_FALSE(i0 != nullopt);
    ASSERT_FALSE(nullopt != i0);
    // <
    ASSERT_LT(nullopt, i1);
    ASSERT_FALSE(i0 < nullopt);
    ASSERT_FALSE(i1 < nullopt);
    ASSERT_FALSE(nullopt < i0);
    // <=
    ASSERT_LE(i0, nullopt);
    ASSERT_LE(nullopt, i0);
    ASSERT_LE(nullopt, i1);
    ASSERT_FALSE(i1 <= nullopt);
    // >
    ASSERT_GT(i1, nullopt);
    ASSERT_FALSE(i0 > nullopt);
    ASSERT_FALSE(nullopt > i0);
    ASSERT_FALSE(nullopt > i1);
    // >=
    ASSERT_GE(i0, nullopt);
    ASSERT_GE(i1, nullopt);
    ASSERT_GE(nullopt, i0);
    ASSERT_FALSE(nullopt >= i1);
}

TEST(OptionalTest, ComparisonValue)
{
    Optional<int> i0;
    Optional<int> i1{1};
    Optional<int> i2{2};

    // ==
    ASSERT_EQ(i1, 1);
    ASSERT_EQ(1, i1);
    ASSERT_EQ(i2, 2);
    ASSERT_EQ(2, i2);
    ASSERT_FALSE(i0 == 1);
    ASSERT_FALSE(i0 == 2);
    ASSERT_FALSE(1 == i0);
    ASSERT_FALSE(2 == i0);
    ASSERT_FALSE(i1 == 2);
    ASSERT_FALSE(i2 == 1);
    ASSERT_FALSE(2 == i1);
    ASSERT_FALSE(1 == i2);
    // !=
    ASSERT_NE(i0, 1);
    ASSERT_NE(i0, 2);
    ASSERT_NE(1, i0);
    ASSERT_NE(2, i0);
    ASSERT_NE(i1, 2);
    ASSERT_NE(i2, 1);
    ASSERT_NE(2, i1);
    ASSERT_NE(1, i2);
    ASSERT_FALSE(i1 != 1);
    ASSERT_FALSE(1 != i1);
    ASSERT_FALSE(i2 != 2);
    ASSERT_FALSE(2 != i2);
    // <
    ASSERT_LT(i0, 1);
    ASSERT_LT(i0, 2);
    ASSERT_LT(i1, 2);
    ASSERT_LT(1, i2);
    ASSERT_FALSE(1 < i0);
    ASSERT_FALSE(2 < i0);
    ASSERT_FALSE(i1 < 1);
    ASSERT_FALSE(1 < i1);
    ASSERT_FALSE(2 < i1);
    ASSERT_FALSE(i2 < 1);
    ASSERT_FALSE(i2 < 2);
    ASSERT_FALSE(2 < i2);
    // <=
    ASSERT_LE(i0, 1);
    ASSERT_LE(i0, 2);
    ASSERT_LE(i1, 1);
    ASSERT_LE(i1, 2);
    ASSERT_LE(i2, 2);
    ASSERT_LE(1, i1);
    ASSERT_LE(1, i2);
    ASSERT_LE(2, i2);
    ASSERT_FALSE(i2 <= 1);
    ASSERT_FALSE(1 <= i0);
    ASSERT_FALSE(2 <= i0);
    ASSERT_FALSE(2 <= i1);
    // >
    ASSERT_GT(i2, 1);
    ASSERT_GT(1, i0);
    ASSERT_GT(2, i0);
    ASSERT_GT(2, i1);
    ASSERT_FALSE(i0 > 1);
    ASSERT_FALSE(i0 > 2);
    ASSERT_FALSE(i1 > 1);
    ASSERT_FALSE(i1 > 2);
    ASSERT_FALSE(i2 > 2);
    ASSERT_FALSE(1 > i1);
    ASSERT_FALSE(1 > i2);
    ASSERT_FALSE(2 > i2);
    // >=
    ASSERT_GE(1, i0);
    ASSERT_GE(2, i0);
    ASSERT_GE(i1, 1);
    ASSERT_GE(1, i1);
    ASSERT_GE(2, i1);
    ASSERT_GE(i2, 1);
    ASSERT_GE(i2, 2);
    ASSERT_GE(2, i2);
    ASSERT_FALSE(i0 >= 1);
    ASSERT_FALSE(i0 >= 2);
    ASSERT_FALSE(i1 >= 2);
    ASSERT_FALSE(1 >= i2);
}

TEST(OptionalTest, swap)
{
    Optional<int> i0;
    Optional<int> i1{g_intTest};
    Optional<std::string> s0;
    Optional<std::string> s1{g_strTest};

    ASSERT_TRUE(!i0);
    ASSERT_FALSE(!i1);
    ASSERT_TRUE(!s0);
    ASSERT_FALSE(!s1);
    ASSERT_EQ(*i1, g_intTest);
    ASSERT_EQ(*s1, g_strTest);

    nosal::core::swap(i0, i1);
    nosal::core::swap(s1, s0);
    ASSERT_FALSE(!i0);
    ASSERT_TRUE(!i1);
    ASSERT_FALSE(!s0);
    ASSERT_TRUE(!s1);
    ASSERT_EQ(*i0, g_intTest);
    ASSERT_EQ(*s0, g_strTest);

    i1.swap(i0);
    s0.swap(s1);
    ASSERT_TRUE(!i0);
    ASSERT_FALSE(!i1);
    ASSERT_TRUE(!s0);
    ASSERT_FALSE(!s1);
    ASSERT_EQ(*i1, g_intTest);
    ASSERT_EQ(*s1, g_strTest);
}

TEST(OptionalTest, make_optional)
{
    Optional<int> v1{g_intTest};
    Optional<std::string> v2{g_strTest};
    auto t11 = nosal::core::make_optional<int>(int{g_intTest});
    auto t12 = nosal::core::make_optional<const int>(int{g_intTest});
    auto t13 = nosal::core::make_optional<int&>(int{g_intTest});
    auto t14 = nosal::core::make_optional<const int&>(int{g_intTest});
    auto t15 = nosal::core::make_optional<int&&>(int{g_intTest});
    auto t16 = nosal::core::make_optional<const int&&>(int{g_intTest});
    auto t21 = nosal::core::make_optional<std::string>(std::string{g_strTest});
    auto t22 = nosal::core::make_optional<const std::string>(std::string{g_strTest});
    auto t23 = nosal::core::make_optional<std::string&>(std::string{g_strTest});
    auto t24 = nosal::core::make_optional<const std::string&>(std::string{g_strTest});
    auto t25 = nosal::core::make_optional<std::string&&>(std::string{g_strTest});
    auto t26 = nosal::core::make_optional<const std::string&&>(std::string{g_strTest});
    static_assert(std::is_same<decltype(v1), decltype(t11)>::value, "Type mismatch");
    static_assert(std::is_same<decltype(v1), decltype(t12)>::value, "Type mismatch");
    static_assert(std::is_same<decltype(v1), decltype(t13)>::value, "Type mismatch");
    static_assert(std::is_same<decltype(v1), decltype(t14)>::value, "Type mismatch");
    static_assert(std::is_same<decltype(v1), decltype(t15)>::value, "Type mismatch");
    static_assert(std::is_same<decltype(v1), decltype(t16)>::value, "Type mismatch");
    static_assert(std::is_same<decltype(v2), decltype(t21)>::value, "Type mismatch");
    static_assert(std::is_same<decltype(v2), decltype(t22)>::value, "Type mismatch");
    static_assert(std::is_same<decltype(v2), decltype(t23)>::value, "Type mismatch");
    static_assert(std::is_same<decltype(v2), decltype(t24)>::value, "Type mismatch");
    static_assert(std::is_same<decltype(v2), decltype(t25)>::value, "Type mismatch");
    static_assert(std::is_same<decltype(v2), decltype(t26)>::value, "Type mismatch");
    ASSERT_EQ(*t11, g_intTest);
    ASSERT_EQ(*t12, g_intTest);
    ASSERT_EQ(*t13, g_intTest);
    ASSERT_EQ(*t14, g_intTest);
    ASSERT_EQ(*t15, g_intTest);
    ASSERT_EQ(*t16, g_intTest);
    ASSERT_EQ(*t21, g_strTest);
    ASSERT_EQ(*t22, g_strTest);
    ASSERT_EQ(*t23, g_strTest);
    ASSERT_EQ(*t24, g_strTest);
    ASSERT_EQ(*t25, g_strTest);
    ASSERT_EQ(*t26, g_strTest);
}

TEST(OptionalTest, hash)
{
    Optional<int> i0;
    Optional<int> i1{g_intTest};
    Optional<std::string> s0;
    Optional<std::string> s1{g_strTest};

    EXPECT_NO_THROW(nosal::core::hash<decltype(i0)>()(i0));
    EXPECT_NO_THROW(nosal::core::hash<decltype(s0)>()(s0));

    ASSERT_EQ(nosal::core::hash<decltype(i1)>()(i1), std::hash<std::decay<decltype(g_intTest)>::type>()(g_intTest));
    ASSERT_EQ(nosal::core::hash<decltype(s1)>()(s1), std::hash<std::decay<decltype(g_strTest)>::type>()(g_strTest));
}
